﻿using GameSave.Common.Entities;
using GameSave.Common.Misc;
using GameSave.Infrastructure.Entities;
using GameSave.Infrastructure.Interfaces;
using GameSave.Infrastructure.Message;
using log4net;
using Quick;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace GameSave.Impl.JobExecutor
{
    public class DirectoryChangedDependJobExecutor : IJobExecutor
    {
        private Job _currentJob;
        private SysConfig _sysContext;
        private ILog logger = LogManager.GetLogger(nameof(TimerDependJobExecutor));
        private FileSystemWatcherEx _fileSysWatchEx;
        Task BeforeTask = null;
        Task AfterTask = null;
        private int defaultBackupColdDown = 30;
        private int defaultFileModifyIgnoreTime = 5;
        CancellationTokenSource beforeCancelTokenSource;
        CancellationTokenSource afterCancelTokenSource;
        private DateTime lastFileChangeDateTime;
        private DateTime lastExecuteBackupDateTime;
        private IMessenger messenger;


        public void InitExecutor(JobContext context, SysConfig sysContext, IMessenger msger)
        {
            _currentJob = context.CurrentJob;
            _sysContext = sysContext;
            messenger = msger;
            lastFileChangeDateTime = DateTime.Now;
            lastExecuteBackupDateTime = DateTime.Now;
            if (sysContext.BackupTriggerColdDown > 0)
            {
                defaultBackupColdDown = sysContext.BackupTriggerColdDown;
            }
            string monitorDirectory = _currentJob.TargetBackupDirectory;
            _fileSysWatchEx = new FileSystemWatcherEx(monitorDirectory, 
                "*.*", 
                true, 
                "",
                OnFileCreated, 
                null,
                OnFileChanged);
        }

        private void OnFileCreated(object sender, System.IO.FileSystemEventArgs e)
        {

        }


        private void OnFileChanged(object sender, System.IO.FileSystemEventArgs e)
        {

            //保底CD: 2倍于备份冷却延迟时间，即因冷却间隔或内置CD而不断被丢弃存档变更，防止从头到尾备份动作被不停取消，而不产生任何存档
            //约定在一段间隔后强制的执行一次备份存档动作
            int skipIntervalTime = (int)(DateTime.Now - lastExecuteBackupDateTime).TotalSeconds;
            if (skipIntervalTime > defaultBackupColdDown)
            {
                ResolveChange(e);
                lastExecuteBackupDateTime = DateTime.Now;
                return;
            }

            //内置CD：5秒，即5秒内产生第二次文件变更，无条件的不作处理
            int fileChangeIntervalTime = (int)(DateTime.Now - lastFileChangeDateTime).TotalSeconds;
            lastFileChangeDateTime = DateTime.Now;
            if (fileChangeIntervalTime <= defaultFileModifyIgnoreTime)
            {
                return;
            }

            //短时间的写入多个文件,触发多次 情况下，只响应最后一次文件变更，防止因游戏存档频繁写入产生大量雷同的存档
            if (BeforeTask == null || BeforeTask.IsCanceled == true || BeforeTask.IsFaulted == true || BeforeTask.IsCompleted == true)
            {
                beforeCancelTokenSource = new CancellationTokenSource();
                if(AfterTask != null && !AfterTask.IsCompleted && AfterTask.IsCanceled != false)
                {
                    afterCancelTokenSource.Cancel();
                    Debug.WriteLine("Cancel After Task;");

                }
                BeforeTask = Task.Run(() =>
                {
                    Thread.Sleep(TimeSpan.FromSeconds(defaultBackupColdDown));
                    if(beforeCancelTokenSource.IsCancellationRequested)
                    {
                        return;
                    }
                    Debug.WriteLine("Execute Before Task;");
                    ResolveChange(e);
                    lastExecuteBackupDateTime = DateTime.Now;
                }, beforeCancelTokenSource.Token); 
            }

            else if(AfterTask == null || AfterTask.IsCanceled == true || AfterTask.IsFaulted == true || AfterTask.IsCompleted == true)
            {
                //取消先前任务
                beforeCancelTokenSource.Cancel();
                Debug.WriteLine("Cancel Before Task;");

                afterCancelTokenSource = new CancellationTokenSource();
                AfterTask = Task.Run(() =>
                {
                    Thread.Sleep(TimeSpan.FromSeconds(defaultBackupColdDown));
                    if (afterCancelTokenSource.IsCancellationRequested)
                    {
                        return;
                    }
                    Debug.WriteLine("Execute After Task;");
                    ResolveChange(e);
                    lastExecuteBackupDateTime = DateTime.Now;
                }, afterCancelTokenSource.Token);
            };
        }

        private void ResolveChange(System.IO.FileSystemEventArgs e)
        {
            if (e.ChangeType == System.IO.WatcherChangeTypes.Created
                || e.ChangeType == System.IO.WatcherChangeTypes.Changed)
            {
                //检测到文件变动，备份整个文件夹
                string backupBaseDir = _sysContext.SaveDirectory;
                string jobDir = Path.Combine(backupBaseDir, _currentJob.JobId);

                if (!Directory.Exists(jobDir))
                {
                    Directory.CreateDirectory(jobDir);
                }

                DateTime now = DateTime.Now;
                string backUpdirName = $"{now.ToString("yyyyMMdd")}_{now.ToString("HHmmss")}";
                string backupRelativePath = $"{_currentJob.JobId}\\{backUpdirName}";

                string backUpDir = Path.Combine(jobDir, backUpdirName);
                if (!Directory.Exists(backUpDir))
                {
                    Directory.CreateDirectory(backUpDir);
                }

                List<string> sourceFileList = new List<string>();
                if (Directory.Exists(_currentJob.TargetBackupDirectory))
                {
                    DirectoryInfo sourceDI = new DirectoryInfo(_currentJob.TargetBackupDirectory);
                    FileInfo[] backupFileList = sourceDI.GetFiles();
                    foreach (FileInfo fi in backupFileList)
                    {
                        sourceFileList.Add(fi.FullName);
                    }


                    if (sourceFileList != null && sourceFileList.Any())
                    {
                        foreach (string fileFullPath in sourceFileList)
                        {
                            string fileName = Path.GetFileName(fileFullPath);
                            string destFileFullPath = Path.Combine(backUpDir, fileName);
                            File.Copy(fileFullPath, destFileFullPath, true);
                        }

                        //更新备份状态
                        _currentJob.LastExecuteDate = now;
                        _currentJob.SaveOperationCount += 1;

                        //引发备份后事件
                        string serialNumText = string.Format("{0:D3}", _currentJob.SaveOperationCount);
                        string desc = $"存档{serialNumText}";
                        messenger.Send<NewBackupCreateMessage>(new NewBackupCreateMessage
                        { 
                            NewBackup = new GameSaveModel
                            {
                                CreateDate = now,
                                GameSaveSerialNum = _currentJob.SaveOperationCount,
                                JobID = _currentJob.JobId,
                                Desc = desc,
                                BackupRelativePath = backupRelativePath,
                                IsAutoSave = true
                            }

                        });
                    }
                    else
                    {
                        logger.Error($"当前任务(Name='{_currentJob.JobName}',Id='{_currentJob.JobId}')\n未找到可以备份的文件，备份结束!");
                    }
                }
                else
                {
                    logger.Error($"当前任务(Name='{_currentJob.JobName}',Id='{_currentJob.JobId}')\n设置的源文件({_currentJob.TargetBackupDirectory})不存在，备份失败!");
                }
            }
        }

        public void Start()
        {
            if (_fileSysWatchEx != null)
            {
                _fileSysWatchEx.Start();
                logger.Info($"Job已启动(Name='{_currentJob.JobName}',Id='{_currentJob.JobId}')");
            }
        }

        public void Stop()
        {
            if (_fileSysWatchEx != null)
            {
                _fileSysWatchEx.Stop();
                logger.Info($"Job已停止(Name='{_currentJob.JobName}',Id='{_currentJob.JobId}')");
            }
        }
    }
}
